/*
 * Copyright (c) 2024 Shenzhen Kaihong Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import hilog from '@ohos.hilog';

export interface ProgressListener {
  updateProgressNotification();
}

export class DfuProgressInfo {
  private DOMAIN = 0x8632;
  private TAG = 'DfuProgressInfo';

  private mListener: ProgressListener;

  private progress: number;
  private bytesSent: number;
  private initialBytesSent: number;
  private lastBytesSent: number;
  private bytesReceived: number;
  private imageSizeInBytes: number;
  private maxObjectSizeInBytes: number;
  private currentPart: number;
  private totalParts: number;
  private timeStart: number;
  private lastProgressTime: number;

  constructor(listener: ProgressListener) {
    this.mListener = listener;
  }

  public init(imageSizeInBytes: number, currentPart: number, totalParts: number) {
    const INTEGER_MAX_VALUE = 2 ** 31 - 1;
    this.imageSizeInBytes = imageSizeInBytes;
    this.maxObjectSizeInBytes = INTEGER_MAX_VALUE; // by default the whole firmware will be sent as a single object
    this.currentPart = currentPart;
    this.totalParts = totalParts;
  }

  public setTotalPart(totalParts: number): DfuProgressInfo {
    this.totalParts = totalParts;
    return this;
  }

  public setProgress(progress: number) {
    this.progress = progress;
    this.mListener.updateProgressNotification();
  }

  public setBytesSent(bytesSent: number) {
    if (this.timeStart == 0) {
      this.timeStart = Date.now();
      this.initialBytesSent = bytesSent;
    }
    this.bytesSent = bytesSent;
    this.progress = (100 * bytesSent / this.imageSizeInBytes);
    hilog.info(this.DOMAIN, this.TAG, `setBytesSent: ${this.bytesSent}`);
    this.mListener.updateProgressNotification();
  }

  public addBytesSent(increment: number) {
    hilog.info(this.DOMAIN, this.TAG, `addBytesSent: ${increment}}`);
    this.setBytesSent(this.bytesSent + increment);
  }

  public setBytesReceived(bytesReceived: number) {
    this.bytesReceived = bytesReceived;
  }

  public setMaxObjectSizeInBytes(bytes: number) {
    this.maxObjectSizeInBytes = bytes;
  }

  public isComplete(): boolean {
    hilog.info(this.DOMAIN, this.TAG, `isComplete: ${this.bytesSent}`)
    return this.bytesSent == this.imageSizeInBytes;
  }

  public isObjectComplete(): boolean {
    hilog.info(this.DOMAIN, this.TAG, `isObjectComplete: ${this.bytesSent}}`);
    return (this.bytesSent % this.maxObjectSizeInBytes) == 0;
  }

  public getAvailableObjectSizeIsBytes(): number {
    let remainingBytes: number = this.imageSizeInBytes - this.bytesSent;
    let remainingChunk: number = this.maxObjectSizeInBytes - (this.bytesSent % this.maxObjectSizeInBytes);
    return Math.min(remainingBytes, remainingChunk);
  }

  public getProgress(): number {
    return this.progress;
  }

  public getBytesSent(): number {
    return this.bytesSent;
  }

  public getBytesReceived(): number {
    return this.bytesReceived;
  }

  public getImageSizeInBytes(): number {
    return this.imageSizeInBytes;
  }

  public getSpeed(): number {
    let now = Date.now();
    let speed: number = now - this.timeStart != 0 ?
      (this.bytesSent - this.lastBytesSent) / (now - this.lastProgressTime) : 0;
    this.lastProgressTime = now;
    this.lastBytesSent = this.bytesSent;
    return speed;
  }

  public getAverageSpeed(): number {
    let now: number = Date.now();
    return now - this.timeStart != 0 ? (this.bytesSent - this.initialBytesSent) / (now - this.timeStart) : 0;
  }

  public getCurrentPart(): number {
    return this.currentPart;
  }

  public getTotalParts(): number {
    return this.totalParts;
  }

  public isLastPart(): boolean {
    return this.currentPart == this.totalParts;
  }
}